%{

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "win32define.h"

#include "eoutput.h"

#include "stack.h"
#include "symboles.h"
#include "instructions.h"
#include "functions.h"
#include "loops.h"

/* Juste des définitions pour éviter que le compilo ne crache... */
extern int yyerror (char *) ; 
extern int yylex (void) ;

int G_BlockLevel = 0 ;

/****************/
/* INSTRUCTIONS */
/****************/

/* Contient le nom de la fonction courrante. */
struct Symbol *G_CurrentFunction = NULL ;
int G_CurrentReturn = 0 ;

/*********/
/* SAUTS */
/*********/

/* Contient le numéro de l'étiquette de label courrante. */
int G_CurrentJLabelNum = 0 ;

/************/
/* SYMBOLES */
/************/

struct SType createSType (enum Type type, int cst) ;

/* Variable contenant le type actuel d'une déclaration. */
struct SType G_CurrentType ;

/* Variable stockant la taille des données de la fonction. */
int G_LocalStackSize = 0 ;

/*************/
/* REGISTRES */
/*************/

/* Pointe en permanence sur le prochain registre libre. */
int G_CurrentReg = 0;

struct RegisterValue createRegVal (int num, struct SType sType) ;

%}

%left tCOMA
%right tOP_AFC tOP_AFPLUS tOP_AFLESS tOP_AFMUL tOP_AFDIV tOP_AFLSHIFT tOP_AFRSHIFT tOP_AFAND tOP_AFOR tOP_AFXOR tOP_AFNOT
%right tOP_TER
%left tOP_OR
%left tOP_AND
%left tOP_BOR
%left tOP_BXOR
%left tOP_BAND
%left tOP_EQ tOP_NE
%left tOP_LT tOP_LE tOP_GT tOP_GE
%left tOP_LSHIFT tOP_RSHIFT
%left tOP_PLUS tOP_LESS
%left tOP_MUL tOP_DIV
%right tOP_NOT tOP_BNOT tOP_PPLUS tOP_LLESS
%left PREC_PA

%nonassoc IF_ALONE
%nonassoc tELSE

%union {
    int val ; 
    char *id ;
    char *comment ; 
    char *str ;
    struct Symbol *sym ;
    struct RegisterValue reg ;
    enum JumpCondType typeCmp ; 
    struct SType sType ;
    struct SType *pSType ;
    enum OperationType typeOp ; 
    int labelNumber ;
}
%type <sType> Type
%type <sym> FunctionParam
%type <sym> FunctionParams
%type <pSType> FunctionArg
%type <pSType> FunctionArgs
%type <reg> Operation
%type <reg> RightOperande
%type <reg> RightOperandes
%type <reg> Affectation
%type <reg> Affectations
%type <reg> FunctionCall
%type <sym> LeftOperande
%type <sym> FunctionHeader
%type <typeCmp> Comparator
%type <labelNumber> Comparison
%type <labelNumber> IfBlock
%type <typeOp> AfcRightOperator
%type <typeOp> PrePostOperator
%token <val> tINTEGER
%token <id> tVARIABLE
%token <str> tSTRING
%token <comment> tCOMMENT_LINE
%token tCONST
%token tINT
%token tVOID
%token tCOMA
%token tRETURN
%token tEND
%token tASM
%token tCOLON
%token tOP_TER
%token tAC_OPEN tAC_CLOSE tPA_OPEN tPA_CLOSE 
%token tOP_PLUS tOP_LESS tOP_MUL tOP_DIV // + - * /
%token tOP_LSHIFT tOP_RSHIFT // << >>
%token tOP_BAND tOP_BOR tOP_BXOR tOP_BNOT // & | ^ ~
%token tOP_PPLUS tOP_LLESS // ++ -- 
%token tOP_AFC // =
%token tOP_AFPLUS tOP_AFLESS tOP_AFMUL tOP_AFDIV // += -= *=
%token tOP_AFLSHIFT tOP_AFRSHIFT // <<= >>=
%token tOP_AFAND tOP_AFXOR tOP_AFOR tOP_AFNOT // &= |= ^= ~=
%token tOP_EQ tOP_NE tOP_GT tOP_GE tOP_LT tOP_LE
%token tOP_AND tOP_OR tOP_NOT
%token tIF tELSE
%token tWHILE tFOR tBREAK tCONTINUE

%error-verbose

%start FileBody
			
%%

FileBody:
 | FunctionDeclaration FileBody
 | FunctionDefinition FileBody
 ;

Comment: tCOMMENT_LINE {
    INS_Comment($1) ;
} ;

ASMRoutine: tASM tSTRING {
    INS_AddRoutine($2) ;
} ;

/* Met à jour le type courrant (utile si déclaration multiple). */
Type: tCONST { 
    G_CurrentType = createSType (INT, 1) ;
    $$ = G_CurrentType ;
}
| tINT { 
    G_CurrentType = createSType (INT, 0) ;
    $$ = G_CurrentType ;
}
| tVOID {
    G_CurrentType = createSType (VOID, 0) ;
    $$ = G_CurrentType ;
}
| tCONST tINT { 
    G_CurrentType = createSType (INT, 1) ;
    $$ = G_CurrentType ;
};

FunctionParam: Type tVARIABLE {
    $$ = createVariableSymbol ($2, 1, $1) ;
} 
| Type {
    $$ = createVariableSymbol (NULL, 1, $1) ;
};

FunctionParams: FunctionParam { $$ = $1 ; }
| FunctionParam tCOMA FunctionParams { $1->sNext = $3 ; $$ = $1 ; }  ;

/* Le header retourne un pointeur de symbole un peu spécial. Le pointeur
   pointe sur le symbole de la fonction, et les symboles suivantes sont
   les arguments de la fonction ! */
FunctionHeader: Type tVARIABLE tPA_OPEN FunctionParams tPA_CLOSE  {
    struct Symbol *args = $4 ;
    struct SType *type = NULL, *start = NULL ;
    while ($4) {
        if (type) {
            if ($4->sType->ssType == VOID) {
                newError("Syntax error.") ;
            }
            type->ssNextArg = malloc(sizeof(struct SType)) ;
            type = type->ssNextArg ;
        }
        else {
            type = malloc(sizeof(struct SType)) ;
            start = type ;
        }
        *type = *($4->sType) ;
        $4 = $4->sNext ;
    }
    $$ = createFunctionSymbol ($2, $1, start) ;
    $$->sNext = args ;
} ;

FunctionDeclaration: FunctionHeader tEND {
    struct Symbol *arg  ;
    while ($1->sNext) {
        arg = $1->sNext ;
        $1->sNext = $1->sNext->sNext ;
        free(arg) ;
    }
    switch (addSymbol($1)) {
    case SYM_REDEFINED:
	newError("Redeclaration of function '%s'.", $1->sName) ;
	break ;
    default:
	break ;
    } ;
} ;

FunctionDefinition: FunctionHeader {
    const char *labelReserve ;
    struct Symbol *args = $1->sNext, *aux ;
    if (!isSymbolAtLevel($1->sName, $1->sBlockLevel)) {
	addSymbol($1) ;
    }  
    else {
        if (!cmpType($1->sType, getSymbolAtLevel($1->sName, $1->sBlockLevel)->sType)) {
            newError("Redefinition of '%s' as different kind of function.", $1->sName) ;
        }
    }
    if ($1->sInit) {
	newError("Redefinition of function '%s'", $1->sName) ;
    }
    /* On ajoute les paramètres à la table des symboles ! */
    if (args->sType->ssType == VOID && args->sType->ssNextArg == NULL) { }
    else {
	while (args) {
            args->sInit = 1 ;
            aux = args->sNext ;
            if (!args->sName) {
                newError("Parameter name omitted.") ;
                args->sName = "" ; // Pour éviter les segfaults
            }
	    addSymbol(args) ; 
            args = aux ;
	}
    }
    /* On initialise les variables globales. */
    $1->sInit = 1 ;
    G_CurrentFunction = $1 ;
    G_CurrentReg = 0 ;
    G_CurrentReturn = 0 ;
    G_LocalStackSize = 0 ;
    INS_Label(G_CurrentFunction->sName) ;
    INS_Push(EBP) ;
    INS_Mov (EBP, ESP) ;
    labelReserve = getLabelReserveSize(G_CurrentFunction->sName) ;
    INS_Operation(OP_SUB, "DL", ESP, labelReserve) ;
    free((void*)labelReserve) ;
    G_BlockLevel ++ ;
} BlockBody { 
    G_BlockLevel -- ;
    removeMaxLevelSymbols(1, NULL) ;
    const char *labelReserve = getLabelReserveSize(G_CurrentFunction->sName);
    pushLabelSymbol(labelReserve, G_LocalStackSize) ;
    free((void*)labelReserve) ;
    INS_Leave() ;
    if (G_CurrentFunction->sType->ssType != VOID && !G_CurrentReturn) {
        newWarning("Control reaches end of non-void function.") ;
    }
} ;

/* Tous les opérateurs faisant une affectation en plus. */
AfcRightOperator: tOP_AFPLUS { $$ = OP_ADD ; }
 | tOP_AFLESS { $$ = OP_SUB ; }
 | tOP_AFMUL { $$ = OP_MUL ; }
 | tOP_AFDIV { $$ = OP_DIV ; }
 | tOP_AFLSHIFT { $$ = OP_LSHIFT ; }
 | tOP_AFRSHIFT { $$ = OP_RSHIFT ; }
 | tOP_AFAND { $$ = OP_AND ; }
 | tOP_AFXOR { $$ = OP_XOR ; }
 | tOP_AFOR  { $$ = OP_OR ; }
 | tOP_AFNOT { $$ = OP_NOT ; }
;

/* Les opérateurs ++ et -- */
PrePostOperator: tOP_PPLUS { $$ = OP_ADD ; }
| tOP_LLESS { $$ = OP_SUB ; }
 ;

LeftOperande: tVARIABLE {
    struct Symbol *sym = getSymbolUnderLevel($1, G_BlockLevel) ;
    if (!sym) {
	newError("Variable '%s' used but not declared.", $1) ;
    }
    $$ = sym ;
} ;

RightOperande: LeftOperande {
    if ($1) {
        if (!$1->sInit) {
            $1->sInit = 1 ;
            newWarning("Variable '%s' used but not initialized.", $1->sName) ;
        }
        if ($1->sBlockLevel == 1) {
            INS_LoadFromRegOffset (REGISTER(G_CurrentReg), EBP, $1->sAddr);
        }
        else {
            INS_LoadFromRegOffset (REGISTER(G_CurrentReg), EBP, -$1->sAddr);
        }
        $$ = createRegVal(G_CurrentReg, *$1->sType) ;
    }
    G_CurrentReg ++ ;  
}
| tINTEGER {
    INS_Afc(REGISTER(G_CurrentReg), $1) ;
    $$ = createRegVal(G_CurrentReg, createSType(INT, 1)) ;
    G_CurrentReg ++ ;
}
| tOP_LESS tINTEGER {
    INS_Afc(REGISTER(G_CurrentReg), -$2) ;
    $$ = createRegVal(G_CurrentReg, createSType(INT, 1)) ;
    G_CurrentReg ++ ;
}
| tOP_PLUS tINTEGER {
    INS_Afc(REGISTER(G_CurrentReg), $2) ;
    $$ = createRegVal(G_CurrentReg, createSType(INT, 1)) ;
    G_CurrentReg ++ ;
}
| Affectation {
    $$ = $1 ;
    G_CurrentReg ++ ;
}
| Operation {
    $$ = $1 ;
} 
| FunctionCall {
    $$ = $1 ;
    if ($$.num < 0) {
        newError("Right-Value cannot be void-returning function.") ;
    }
    G_CurrentReg ++ ;
} ;

RightOperandes: RightOperande 
| Comparison tOP_TER RightOperande {
    INS_Mov(REGISTER(-- G_CurrentReg), REGISTER($3.num)) ;
    $<labelNumber>$ = G_CurrentJLabelNum ++ ;
    INS_JumpUC($<labelNumber>$) ;
    INS_JumpLabel($1) ;
} tCOLON RightOperande {
    INS_Mov(REGISTER(-- G_CurrentReg), REGISTER($6.num)) ;
    INS_JumpLabel($<labelNumber>4) ;
    $$ = createRegVal(G_CurrentReg, $3.type) ;
} %prec tOP_TER
| Comparison {
    int lab = G_CurrentJLabelNum ++ ;
    INS_Afc(REGISTER(G_CurrentReg), 1) ;
    INS_JumpUC (lab) ;
    INS_JumpLabel($1) ;
    INS_Afc(REGISTER(G_CurrentReg), 0) ;
    INS_JumpLabel(lab) ;
    $$ = createRegVal(G_CurrentReg ++, createSType(INT, 1)) ;
} 
| RightOperandes tCOMA RightOperande ;

/* 
  Opération, retourne le registre contenant la valeur du résultat. 
*/
Operation: tPA_OPEN RightOperandes tPA_CLOSE { 
    $$ = $2 ; 
} %prec PREC_PA
| RightOperande tOP_MUL RightOperande {
    INS_Operation(OP_MUL, "DD", REGISTER($1.num), REGISTER($3.num)) ;
    G_CurrentReg -- ;
    $$ = $1;
}
| RightOperande tOP_DIV RightOperande {
    INS_Operation(OP_DIV, "DD", REGISTER($1.num), REGISTER($3.num)) ;
    G_CurrentReg -- ;
    $$ = $1;
}
| RightOperande tOP_PLUS RightOperande {
    INS_Operation(OP_ADD, "DD", REGISTER($1.num), REGISTER($3.num)) ;
    G_CurrentReg -- ;
    $$ = $1;
}
| RightOperande tOP_LESS RightOperande {
    INS_Operation(OP_SUB, "DD", REGISTER($1.num), REGISTER($3.num)) ;
    G_CurrentReg -- ;
    $$ = $1;
}
| RightOperande tOP_LSHIFT RightOperande {
    INS_Operation(OP_LSHIFT, "DD", REGISTER($1.num), REGISTER($3.num)) ;
    G_CurrentReg -- ;
    $$ = $1;
}
| RightOperande tOP_RSHIFT RightOperande {
    INS_Operation(OP_RSHIFT, "DD", REGISTER($1.num), REGISTER($3.num)) ;
    G_CurrentReg -- ;
    $$ = $1;
}
| RightOperande tOP_BAND RightOperande {
    INS_Operation(OP_AND, "DD", REGISTER($1.num), REGISTER($3.num)) ;
    G_CurrentReg -- ;
    $$ = $1;
}
| RightOperande tOP_BXOR RightOperande {
    INS_Operation(OP_XOR, "DD", REGISTER($1.num), REGISTER($3.num)) ;
    G_CurrentReg -- ;
    $$ = $1;
}
| RightOperande tOP_BOR RightOperande {
    INS_Operation(OP_OR, "DD", REGISTER($1.num), REGISTER($3.num)) ;
    G_CurrentReg -- ;
    $$ = $1;
}
| tOP_BNOT RightOperande {
    INS_Operation(OP_NOT, "DD", REGISTER($2.num), REGISTER($2.num)) ;
    $$ = $2 ;
} ;

Comparator: tOP_EQ { $$ = JUMP_EQ ; } 
| tOP_NE { $$ = JUMP_NE ; }
| tOP_GT { $$ = JUMP_GT ; }
| tOP_LT { $$ = JUMP_LT ; }
| tOP_GE { $$ = JUMP_GE ; }
| tOP_LE { $$ = JUMP_LE ; } ;

Comparison: RightOperande {
    int lElse = G_CurrentJLabelNum++ ;

    INS_Operation(OP_CMP, "DV", REGISTER($1.num), 0) ;
    G_CurrentReg -- ;

    INS_Jump(JUMP_EQ, lElse) ;
    $$ = lElse ;
}
| tPA_OPEN Comparison tPA_CLOSE { $$ = $2 ; } %prec PREC_PA
| RightOperande Comparator RightOperande {
    int lElse = G_CurrentJLabelNum++ ;

    INS_Operation(OP_CMP, "DD", REGISTER($1.num), REGISTER($3.num)) ;
    G_CurrentReg -= 2 ;

    INS_Jump(REVERSE_JUMP($2), lElse) ;
    $$ = lElse ;
} 
| Comparison tOP_AND Comparison {
    INS_JumpUC (G_CurrentJLabelNum) ;
    INS_JumpLabel ($3) ;
    INS_JumpUC ($1) ;
    INS_JumpLabel(G_CurrentJLabelNum ++) ;
    $$ = $1 ;
}
| Comparison tOP_OR {
    $<labelNumber>$ = G_CurrentJLabelNum ++ ;
    INS_JumpUC ($<labelNumber>$) ;
    INS_JumpLabel($1) ;
} Comparison {
    INS_JumpLabel ($<labelNumber>3) ;
    $$ = $4 ;
}
| tOP_NOT Comparison {
    $$ = G_CurrentJLabelNum ++ ;
    INS_JumpUC($$) ;
    INS_JumpLabel($2) ;
} ;

Affectation: LeftOperande tOP_AFC RightOperande %prec tOP_AFC { 
    if ($1) {
	if ($1->sType->ssConst) {
	    newError("Assignment of constant '%s'.", $1->sName) ;
	}
        $1->sInit = 1 ;
        if ($1->sBlockLevel == 1) {
            INS_StoreFromRegOffset(REGISTER($3.num), EBP, $1->sAddr) ; 
        }
        else {
            INS_StoreFromRegOffset(REGISTER($3.num), EBP, -$1->sAddr) ; 
        }
	G_CurrentReg -- ;
    }
    $$ = $3 ;
} 
| LeftOperande {
    if ($1) { 
	if ($1->sType->ssConst) {
	    newError("Assignment of constant '%s'.", $1->sName) ;
	}
        if (!$1->sInit) {
            $1->sInit = 1 ;
	    newWarning("Variable '%s' used but not initialized.", $1->sName) ;
        }
        if ($1->sBlockLevel == 1) {
            INS_LoadFromRegOffset (REGISTER(G_CurrentReg), EBP, $1->sAddr);
        }
        else {
            INS_LoadFromRegOffset (REGISTER(G_CurrentReg), EBP, -$1->sAddr);
        }
        $<reg>$ = createRegVal(G_CurrentReg ++, *$1->sType) ;
    }
} AfcRightOperator RightOperande %prec tOP_AFC {
    if ($1) {
        INS_Operation($3, "DD", REGISTER($<reg>2.num), REGISTER($<reg>4.num)) ;
        if ($1->sBlockLevel == 1) {
            INS_StoreFromRegOffset(REGISTER($<reg>2.num), EBP, $1->sAddr) ; 
        }
        else {
            INS_StoreFromRegOffset(REGISTER($<reg>2.num), EBP, -$1->sAddr) ; 
        }
    }
    $$ = $<reg>2 ;
    G_CurrentReg -= 2 ;
} 
| PrePostOperator LeftOperande {
    if ($2) {
	if ($2->sType->ssConst) {
	    newError("Assignment of constant '%s'.", $2->sName) ;
	}
	if (!$2->sInit) {
            $2->sInit = 1 ;
	    newWarning("Variable '%s' used but not initialized.", $2->sName) ;
        }
        if ($2->sBlockLevel == 1) {
            INS_LoadFromRegOffset (REGISTER(G_CurrentReg), EBP, $2->sAddr);
        }
        else {
            INS_LoadFromRegOffset (REGISTER(G_CurrentReg), EBP, -$2->sAddr);
        }
        INS_Operation($1, "DV", REGISTER(G_CurrentReg), 1) ;
        if ($2->sBlockLevel == 1) {
            INS_StoreFromRegOffset(REGISTER(G_CurrentReg), EBP, $2->sAddr) ; 
        }
        else {
            INS_StoreFromRegOffset(REGISTER(G_CurrentReg), EBP, -$2->sAddr) ; 
        }
        $$ = createRegVal(G_CurrentReg, *$2->sType) ;
    }
}
| LeftOperande PrePostOperator {
    if ($1) {
	if ($1->sType->ssConst) {
	    newError("Assignment of constant '%s'.", $1->sName) ;
	}
	if (!$1->sInit) {
            $1->sInit = 1 ;
	    newWarning("Variable '%s' used but not initialized.", $1->sName) ;
        }
        if ($1->sBlockLevel == 1) {
            INS_LoadFromRegOffset (REGISTER(G_CurrentReg), EBP, $1->sAddr);
        }
        else {
            INS_LoadFromRegOffset (REGISTER(G_CurrentReg), EBP, -$1->sAddr);
        }
        INS_Mov(REGISTER(G_CurrentReg+1), REGISTER(G_CurrentReg)) ;
        INS_Operation($2, "DV", REGISTER(G_CurrentReg+1), 1) ;
        if ($1->sBlockLevel == 1) {
            INS_StoreFromRegOffset(REGISTER(G_CurrentReg), EBP, $1->sAddr) ; 
        }
        else {
            INS_StoreFromRegOffset(REGISTER(G_CurrentReg), EBP, -$1->sAddr) ; 
        }
        $$ = createRegVal(G_CurrentReg, *$1->sType) ;
    }
};

Affectations: Affectation { $$ = $1 ; }
| Affectations tCOMA Affectation { $$ = $3 ; } ;
   
Declaration: tVARIABLE {
    struct Symbol *sym = createVariableSymbol($1, G_BlockLevel, G_CurrentType) ;
    switch (addSymbol(sym)) {
    case SYM_REDEFINED:
	newError("Redefinition of variable '%s'.", $1) ;
	break ;
    default:
	break ;
    } ;
}
| tVARIABLE tOP_AFC RightOperande {
    struct Symbol *sym = createVariableSymbol($1, G_BlockLevel, G_CurrentType) ;
    sym->sInit = 1 ;
    switch (addSymbol(sym)) {
    case SYM_REDEFINED:
	newError("Redefinition of variable '%s'.", $1) ;
	break ;
    default:
        INS_StoreFromRegOffset(REGISTER($3.num), EBP, -getSymbolUnderLevel($1, G_BlockLevel)->sAddr) ; 
        G_CurrentReg -- ;
	break ;
    } ;
};

Declarations: Declaration
| Declarations tCOMA Declaration ;

DeclarationSet: 
| Type {
    if ($1.ssType == VOID) {
	newError("Declaration of void variable(s).") ;
    }
} Declarations tEND DeclarationSet ;

/* 
  Instruction, retourne le registre contenant le résultat (si il y a).
*/
Instruction: Affectations tEND { G_CurrentReg = 0 ; }
| FunctionCall tEND { }
| ASMRoutine tEND
| tBREAK tEND {
    int val = getTopLoopLabel (END_LOOP) ;
    if (val < 0) {
	newError("Break outside a loop.") ;
    }
    else {
	INS_JumpUC (val) ;
    }
}
| tCONTINUE tEND {
    int val = getTopLoopLabel (START_LOOP) ;
    if (val < 0) {
	newError("Continue outside a loop.") ;
    }
    else {
	INS_JumpUC (val) ;
    }
}
| FunctionReturn tEND { }
| ConditionalBlock
| LoopBlock ;

InstructionSet: 
| Comment InstructionSet
| BlockBody InstructionSet ;

BlockBody: Instruction
| tAC_OPEN { G_BlockLevel++ ; } DeclarationSet InstructionSet tAC_CLOSE { 
    int size = getTotalSymbolSize() ; 
    struct Symbol *unused = NULL, *aux ;
    if (size > G_LocalStackSize) G_LocalStackSize = size ;
    removeMaxLevelSymbols(G_BlockLevel --, &unused) ;
    while (unused) {
        aux = unused ;
        unused = unused->sNext ;
        newWarning("Variable '%s' declared but never used.", aux->sName) ;
        freeSymbol(aux) ;
    } 
} ;

IfBlock: tIF tPA_OPEN Comparison tPA_CLOSE BlockBody { $$ = $3 ; };
 
ConditionalBlock: IfBlock {
    INS_JumpLabel($1) ;
} %prec IF_ALONE
| IfBlock tELSE { 
    $<labelNumber>$ = G_CurrentJLabelNum ++ ; // On a besoin du label final
    INS_JumpUC($<labelNumber>$) ;
    INS_JumpLabel($1) ; // end équivaut ici à else (ce qui sera fait si la condition n'est pas ok) 
} BlockBody { 
    INS_JumpLabel($<labelNumber>3) ;
}  %prec tELSE ; 

WhileBlock: tWHILE { 
    $<labelNumber>$ = G_CurrentJLabelNum ++ ;
    INS_JumpLabel($<labelNumber>$) ; // Le début de la boucle (avant les comparaisons)
    pushLoopLabel ($<labelNumber>$, START_LOOP) ;
} tPA_OPEN Comparison tPA_CLOSE {
    pushLoopLabel ($4, END_LOOP) ;
} BlockBody {
    INS_JumpUC($<labelNumber>2) ; 
    INS_JumpLabel($4) ;
    popLoopLabel (START_LOOP) ;
    popLoopLabel (END_LOOP) ;
} ;

ForBlock: tFOR tPA_OPEN 
RightOperandes tEND {
    $<labelNumber>$ = G_CurrentJLabelNum ++ ;
    INS_JumpLabel($<labelNumber>$) ; // Le début de la boucle (avant les comparaisons)
    pushLoopLabel ($<labelNumber>$, START_LOOP) ;
}
Comparison tEND {
    G_CurrentReg = 0 ;
    startBuffer() ;
}
RightOperandes { 
    stopBuffer() ; 
    G_CurrentReg = 0 ;
} tPA_CLOSE {
    pushLoopLabel ($6, END_LOOP) ;
} BlockBody {
    flushBuffer () ;
    INS_JumpUC($<labelNumber>5) ; 
    INS_JumpLabel($6) ;
    popLoopLabel (START_LOOP) ;
    popLoopLabel (END_LOOP) ;
} ;

LoopBlock: WhileBlock 
| ForBlock ;

FunctionArg: RightOperande {
    INS_Push (REGISTER($1.num)) ;
    G_CurrentReg -- ;
    $$ = malloc(sizeof(struct SType)) ;
    *$$ = $1.type ;
} ;

FunctionArgs: { $$ = NULL ; }
| FunctionArg { $$ = $1 ; }
| FunctionArg tCOMA FunctionArgs { $1->ssNextArg = $3 ; $$ = $1 ; }  ;

/*
  Retourne le numéro du registre contenant le résultat, tous les autres registres
  sont sauvegardés avant l'appel puis restaurés après.
  Si la fonction ne renvoie rien, la valeur de retour sera -1 !
*/
FunctionCall: tVARIABLE { 
    struct Symbol *sym = getFunctionSymbol($1) ;
    if (!sym) {
        newError("Call to undefined function '%s'.", $1) ;
    }
    if (G_CurrentReg) { 
         /* Si on a des registres utilisés (i.e G_CurrentReg > 0), on push puis on pop après. */
         INS_PushX(0, G_CurrentReg - 1) ; 
    }
    $<sym>$ = sym ;
} tPA_OPEN FunctionArgs tPA_CLOSE {
    struct Symbol *sym = $<sym>2 ;
    int numArg ;
    if (sym) {
        switch (numArg = checkFunctionArgs(sym->sType->ssNextArg, $4)) {
	case TOO_MANY_ARGS:
            newError("Too many arguments to function '%s'.", $1) ;
            break ;
	case TOO_FEW_ARGS:
            newError("Too few arguments to function '%s'.", $1) ;
            break ;
	case 0:
            break ;
        default:
            newError("Incompatible type for argument %d of '%s'.", numArg, $1) ;
        }
        INS_Call(sym->sName) ;
        if (sym->sType->ssType != VOID) {
	    INS_Mov(REGISTER(G_CurrentReg), REGISTER(0)) ; 
        }
        INS_Operation(OP_ADD, "DV", ESP, getFunctionArgsSize(sym->sType->ssNextArg)) ;
        $$ = createRegVal(sym->sType->ssType != VOID ? G_CurrentReg : -1, createSType(sym->sType->ssType, 1)) ;
    }
    else {
        $$ = createRegVal(G_CurrentReg, createSType(INT, 1)) ;
    }
    if (G_CurrentReg) { 
        INS_PopX(0, G_CurrentReg - 1) ; 
    }
} ;

FunctionReturn: tRETURN RightOperande {
    G_CurrentReturn = 1 ;
    if (G_CurrentFunction->sType->ssType == VOID) {
        newWarning("Return with value in function returning void.") ;
    }
    else if (G_CurrentFunction->sType->ssType != $2.type.ssType) {
        newError("Incompatible type in return.") ;
    }
    INS_Mov(REGISTER(0), REGISTER($2.num)) ;
    INS_Leave () ;
} 
| tRETURN {
    G_CurrentReturn = 1 ;
    if (G_CurrentFunction->sType->ssType != VOID) {
        newWarning("Return without value in non-void function.") ;
    }
    INS_Leave () ;
};

%%

struct SType createSType (enum Type type, int cst) {
    struct SType res = {type, cst, NULL} ;
    return res ;
}

struct RegisterValue createRegVal (int num, struct SType sType) {
    struct RegisterValue reg = {num, sType} ;
    return reg ;
}

extern FILE *yyin;
extern FILE *G_COutput ;

extern int optind ;
extern char *optarg ;

void printUsage (const char *exec, FILE* stream) {
    fprintf(stream, "Usage: %s input [-o output]\n", exec) ;
}
 
int main (int argc, char *argv[]) {
    int res ;
    char opt ;
    char *tmpName = "output.tmp" ;
    char *outputName = "output.asm" ;
    while ((opt = getopt(argc, argv, "o:h")) != -1) {
	switch (opt) {
	case 'o':
	    outputName = optarg ;
	    break ;
	case 'h':
	    printUsage(argv[0], stdout) ;
	    return 0 ;
	default:
	    printUsage(argv[0], stderr) ;
	    return 1 ;
	}
    }
    if (!(optind < argc)) {
	fprintf(stderr, "No input file specified.\n") ;
	return 1 ;
    } 
    yyin = fopen(argv[optind], "r") ;
    if (!yyin) {
	fprintf(stderr, "Input file \"%s\" not found.\n", argv[optind]) ;
	return 1 ;
    }
    G_COutput = fopen(tmpName, "w") ;
    res = yyparse();
    fclose(G_COutput) ;
    if (getNbErrors()) {
	unlink(tmpName) ;
	return 1 ;
    }
    if (replaceLabels(tmpName, outputName)) {
	fprintf(stderr, "Error replacing labels.\n") ;
    }
    unlink(tmpName) ;
    if (!isSymbolAtLevel("main", 0)) {
	fprintf(stderr, "No main symbol found.\n") ;
    }
    return res ;
}

int yyerror(char *s) {
    newError("Syntax error."); 
    return 1;
}
